package com.ironman.analyze.core.context;

import cn.hutool.core.lang.Tuple;
import cn.hutool.core.util.StrUtil;
import cn.idev.excel.FastExcel;
import com.ironman.analyze.core.compute.entity.*;
import com.ironman.analyze.core.handler.VmgPostHandler;
import com.ironman.common.params.RolParam;
import com.ironman.common.util.AccuracyUtil;
import com.ironman.common.util.MathUtil;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Getter
public class TrainingContext {

    private final List<Tuple> standardParams;

    private final VmgPostHandler vmgPostHandler;

    private final RolParam rolParam;

    private final Map<Date, GpsWindowData> gpsWindowDataMap = new HashMap<>();

    private final Map<Date, HeadingWindowData> headingWindowDataMap = new HashMap<>();

    private final Map<Date, HeartrateWindowData> heartrateWindowData1Map = new HashMap<>();

    private final Map<Date, HeartrateWindowData> heartrateWindowData2Map = new HashMap<>();

    private final Map<Date, RolWindowData> rolWindowDataMap = new HashMap<>();

    private final Map<Date, SpeedWindowData> speedWindowDataMap = new HashMap<>();

    private final Map<String, Map<Date, WindEnvWindowData>> windAllWindowDataMapMap = new HashMap<>();

    private final Map<Date, YawWindowData> yawWindowDataMap = new HashMap<>();

    public TrainingContext(List<Tuple> standardParams, RolParam rolParam) {
        this.standardParams = standardParams;
        this.vmgPostHandler = new VmgPostHandler();
        this.rolParam = rolParam;
    }

    public Tuple standard(String sailStatus, Double param) {
        if (Objects.isNull(param) || StrUtil.isBlank(sailStatus)) {
            return null;
        }
        for (Tuple standardParam : standardParams) {
            String type = standardParam.get(0);
            if (sailStatus.startsWith(type)) {
                Double min = standardParam.get(1);
                Double max = standardParam.get(2);
                if (min < param && param <= max) {
                    return new Tuple(standardParam.get(3), standardParam.get(4));
                }
            }
        }
        return null;
    }

    private List<TrainingData> buildTrainingData(List<Tuple> tupleList, String mHmName) {
        List<TrainingData> trainingDataList = new ArrayList<>();
        for (Tuple tuple : tupleList) {
            Date ts = tuple.get(0);
            String trainingBoatCode = tuple.get(1);
            Map<Date, WindEnvWindowData> dateWindEnvWindowDataMap = windAllWindowDataMapMap.get(trainingBoatCode);
            if (CollectionUtils.isEmpty(dateWindEnvWindowDataMap)) {
                continue;
            }
            WindEnvWindowData windEnvWindowData = dateWindEnvWindowDataMap.get(ts);

            GpsWindowData gpsWindowData = gpsWindowDataMap.get(ts);
            SpeedWindowData speedWindowData = speedWindowDataMap.get(ts);
            HeadingWindowData headingWindowData = headingWindowDataMap.get(ts);

            HeartrateWindowData heartrateWindowData1 = heartrateWindowData1Map.get(ts);
            HeartrateWindowData heartrateWindowData2 = heartrateWindowData2Map.get(ts);

            RolWindowData rolWindowData = rolWindowDataMap.get(ts);
            YawWindowData yawWindowData = yawWindowDataMap.get(ts);

            TrainingData trainingData = new TrainingData();
            trainingData.setTs(ts);
            gps(gpsWindowData, trainingData);
            if (Objects.isNull(trainingData.getLon()) && Objects.isNull(trainingData.getLat())) {
                continue;
            }
            vmg(windEnvWindowData, headingWindowData, speedWindowData, trainingData);
            sailStatusAndPosture(windEnvWindowData, headingWindowData, rolWindowData, rolParam, trainingData);
            leeway(headingWindowData, yawWindowData, trainingData);
            heartrate(heartrateWindowData1, heartrateWindowData2, trainingData);

            standardAndCustomStandard(windEnvWindowData, trainingData);

            trainingData.setSpeed(Objects.nonNull(speedWindowData) ? speedWindowData.getSpeed() : null);
            trainingData.setHeadingT(Objects.nonNull(headingWindowData) ? headingWindowData.getHeadingT() : null);
            trainingData.setHeadingM(Objects.nonNull(headingWindowData) ? headingWindowData.getHeadingM() : null);
            trainingData.setRol(Objects.nonNull(rolWindowData) ? rolWindowData.getRol() : null);
            trainingData.setYaw(Objects.nonNull(yawWindowData) ? yawWindowData.getYaw() : null);

            trainingDataList.add(trainingData);
        }
        String fileName = mHmName + "_" + System.currentTimeMillis() + ".xlsx";
        FastExcel.write(fileName, TrainingData.class)
                .sheet("窗口计算中间值")
                .doWrite(trainingDataList);
        return trainingDataList;
    }

    public Training track(List<Tuple> tupleList, String mHmId, String mHmName) {
        List<String> trackList = buildTrainingData(tupleList, mHmName).stream().map(TrainingData::compress).collect(Collectors.toList());
        return TrainingTrack.builder().mHmId(mHmId).mHmName(mHmName).trackListSize(trackList.size()).trackList(trackList).build();
    }

    public Training report(List<Tuple> tupleList, String mHmId, String mHmName, List<String> sportsman) {
        List<TrainingData> trainingDataList = buildTrainingData(tupleList, mHmName);

        // 平均风速
        Double avgWindSpeed = MathUtil.calculateAvg(trainingDataList.stream().map(TrainingData::getWindSpeed).collect(Collectors.toList()));

        List<Double> vmgList = trainingDataList.stream().map(TrainingData::getVmg).collect(Collectors.toList());
        // 平均VMG
        Double avgVmg = MathUtil.calculateAvg(vmgList);
        // VMG标准差
        Double stdVmg = MathUtil.calculateSampleStdDev(vmgList);

        List<Double> speedList = trainingDataList.stream().map(TrainingData::getSpeed).collect(Collectors.toList());
        // 平均船速
        Double avgSpeed = MathUtil.calculateAvg(speedList);
        // 船速标准差
        Double stdSpeed = MathUtil.calculateSampleStdDev(speedList);

        List<Double> angleList = trainingDataList.stream().map(TrainingData::getAngle).collect(Collectors.toList());
        // 平均角度
        Double avgAngle = MathUtil.calculateAvg(angleList);
        // 角度标准差
        Double stdAngle = MathUtil.calculateSampleStdDev(angleList);
        // 航行时间
        long trainingTime = trainingDataList.stream().filter(n -> StrUtil.isNotBlank(n.getLon()) && StrUtil.isNotBlank(n.getLat())).count();

        TrainingReport.TrainingSpeedReport trainingSpeedReport = new TrainingReport.TrainingSpeedReport(
                avgWindSpeed,
                avgVmg,
                stdVmg,
                avgSpeed,
                stdSpeed,
                avgAngle,
                stdAngle,
                trainingTime
        );

        //#####################################

        Map<String, List<TrainingData>> groupedPostureMap = trainingDataList.stream().collect(Collectors.groupingBy(TrainingData::getPosture));

        List<TrainingData> balanceList = Optional.ofNullable(groupedPostureMap.get("平衡")).orElse(new ArrayList<>());
        // 平衡次数
        int balanceCount = balanceList.size();
        List<Double> balanceRolList = balanceList.stream().map(n -> Math.abs(n.getRol())).collect(Collectors.toList());
        //平均平衡倾角值
        Double avgBalance = MathUtil.calculateAvg(balanceRolList);
        //平衡标准差
        Double stdBalance = MathUtil.calculateSampleStdDev(balanceRolList);

        List<TrainingData> reverseList = Optional.ofNullable(groupedPostureMap.get("反扣")).orElse(new ArrayList<>());
        // 反扣次数
        int reverseCount = reverseList.size();
        List<Double> reverseRolList = reverseList.stream().map(n -> Math.abs(n.getRol())).collect(Collectors.toList());
        //平均反扣倾角值
        Double avgReverse = MathUtil.calculateAvg(reverseRolList);
        //反扣标准差
        Double stdReverse = MathUtil.calculateSampleStdDev(reverseRolList);

        List<TrainingData> tiltList = Optional.ofNullable(groupedPostureMap.get("倾斜")).orElse(new ArrayList<>());
        // 倾斜次数
        int tiltCount = tiltList.size();
        List<Double> tiltRolList = tiltList.stream().map(n -> Math.abs(n.getRol())).collect(Collectors.toList());
        //平均倾斜倾角值
        Double avgTilt = MathUtil.calculateAvg(tiltRolList);
        //倾斜标准差
        Double stdTilt = MathUtil.calculateSampleStdDev(tiltRolList);

        TrainingReport.TrainingPostureReport trainingPostureReport = new TrainingReport.TrainingPostureReport(
                avgBalance,
                stdBalance,
                balanceCount,
                avgReverse,
                stdReverse,
                reverseCount,
                avgTilt,
                stdTilt,
                tiltCount
        );

        //##################################
        Map<String, List<TrainingData>> groupedSailStatusMap = trainingDataList.stream().collect(Collectors.groupingBy(TrainingData::getSailStatus));
        int[] ranges = {0, 5, 10, 15, 20, 180};
        Map<String, Map<String, Map<String, Long>>> rolReport = new HashMap<>();
        for (Map.Entry<String, List<TrainingData>> entry : groupedSailStatusMap.entrySet()) {
            String sailStatus = entry.getKey();
            if (StrUtil.isBlank(sailStatus)) {
                continue;
            }
            Map<String, Map<String, Long>> rangeMap = new HashMap<>();
            List<TrainingData> groupedSailStatusDataList = entry.getValue();
            Map<String, List<TrainingData>> groupedSailStatusAndPostureMap = groupedSailStatusDataList.stream().collect(Collectors.groupingBy(TrainingData::getPosture));
            Map<String, Long> reverseRangeCount = MathUtil.countByRange(Optional.ofNullable(groupedSailStatusAndPostureMap.get("反扣")).orElse(new ArrayList<>()).stream().map(n -> Math.abs(n.getRol())).collect(Collectors.toList()), ranges);
            rangeMap.put("reverse", reverseRangeCount);
            Map<String, Long> tiltRangeCount = MathUtil.countByRange(Optional.ofNullable(groupedSailStatusAndPostureMap.get("倾斜")).orElse(new ArrayList<>()).stream().map(n -> Math.abs(n.getRol())).collect(Collectors.toList()), ranges);
            rangeMap.put("tilt", tiltRangeCount);

            rolReport.put(sailStatus, rangeMap);
        }

        return TrainingReport.builder().mHmId(mHmId).mHmName(mHmName)
                .sportsman(sportsman)
                .speedReport(trainingSpeedReport)
                .postureReport(trainingPostureReport)
                .rolReport(rolReport)
                .build();
    }

    public void standardAndCustomStandard(WindEnvWindowData windEnvWindowData, TrainingData trainingData) {
        Double standardSpeed = null;
        Double standardVmg = null;

        Double customStandardSpeed = null;
        Double customStandardVmg = null;

        if (Objects.nonNull(windEnvWindowData)) {
            String sailStatus = trainingData.getSailStatus();
            Double windSpeed = windEnvWindowData.getWindSpeed();
            Double customWindSpeed = windEnvWindowData.getCustomWindSpeed();
            Tuple standard = standard(sailStatus, windSpeed);
            if (Objects.nonNull(standard)) {
                standardSpeed = standard.get(0);
                standardVmg = standard.get(1);
            }
            Tuple customStandard = standard(sailStatus, customWindSpeed);
            if (Objects.nonNull(customStandard)) {
                customStandardSpeed = customStandard.get(0);
                customStandardVmg = customStandard.get(1);
            }
        }
        trainingData.setStandardSpeed(standardSpeed);
        trainingData.setStandardVmg(standardVmg);

        trainingData.setCustomStandardSpeed(customStandardSpeed);
        trainingData.setCustomStandardVmg(customStandardVmg);
    }

    public void gps(GpsWindowData gpsWindowData, TrainingData trainingData) {
        String longitude = null;
        String latitude = null;
        if (Objects.nonNull(gpsWindowData)) {
            longitude = gpsWindowData.getLongitude();
            latitude = gpsWindowData.getLatitude();
        }
        trainingData.setLon(longitude);
        trainingData.setLat(latitude);
    }

    public void heartrate(HeartrateWindowData heartrateWindowData1, HeartrateWindowData heartrateWindowData2, TrainingData trainingData) {
        Double[] heartrate = new Double[]{null, null};
        if (Objects.nonNull(heartrateWindowData1)) {
            Double heartrate1 = heartrateWindowData1.getHeartrate();
            if (Objects.nonNull(heartrate1)) {
                heartrate[0] = heartrate1;
            }
        }
        if (Objects.nonNull(heartrateWindowData2)) {
            Double heartrate2 = heartrateWindowData2.getHeartrate();
            if (Objects.nonNull(heartrate2)) {
                heartrate[1] = heartrate2;
            }
        }
        trainingData.setHeartrate(heartrate);
    }

    /**
     * a: 矢量移动平均风向(windDirection)
     * b: 移动平均航向(heading)
     * c: 由b与a的计算得出
     * | b-a | ≤ 180°，c = | b-a |；
     * | b-a | ＞ 180°，c = 360° - | b-a |；
     * VMG=船速*COS(c)
     */
    public void vmg(WindEnvWindowData windEnvWindowData, HeadingWindowData headingWindowData, SpeedWindowData speedWindowData, TrainingData trainingData) {
        vmgPostHandler.handler(windEnvWindowData, headingWindowData, speedWindowData, vmgWindowData -> {
            trainingData.setAngle(vmgWindowData.getAngle());
            trainingData.setVmg(vmgWindowData.getVmg());

            trainingData.setCustomAngle(vmgWindowData.getCustomAngle());
            trainingData.setCustomVmg(vmgWindowData.getCustomVmg());

            trainingData.setWindSourceBoatCode(vmgWindowData.getWindSourceBoatCode());
            trainingData.setWindSpeed(vmgWindowData.getWindSpeed());
            trainingData.setWindDirection(vmgWindowData.getWindDirection());
            trainingData.setCustomWindSpeed(vmgWindowData.getCustomWindSpeed());
            trainingData.setCustomWindDirection(vmgWindowData.getCustomWindDirection());
        });
    }

    /**
     * 漂角(leeway)，由headingM与yaw的计算得出
     * ● |headingM-yaw|≤90°，w=headingM-yaw;
     * ● headingM-yaw＞90°， w=headingM-yaw-360°;
     * ● headingM-yaw＜-90°，w=360°+headingM-yaw;
     * ● headingM 和 yaw 任一为空时，w=null;
     * 当姿态为U_P时，leeway = -w
     * 当姿态为U_S时，leeway = w
     * 当姿态为D_P时，leeway = w
     * 当姿态为D_S时，leeway = w
     */
    public void leeway(HeadingWindowData headingWindowData, YawWindowData yawWindowData, TrainingData trainingData) {
        String leeway = null;
        if (Objects.nonNull(headingWindowData) && Objects.nonNull(yawWindowData)) {
            Double headingM = headingWindowData.getHeadingM();
            Double yaw = yawWindowData.getYaw();
            if (Objects.nonNull(headingM) && Objects.nonNull(yaw)) {
                double e = headingM - yaw;
                if (Math.abs(e) <= 90) {

                } else if (e > 90) {
                    e -= 360;
                } else {
                    e += 360;
                }
                Integer w = AccuracyUtil.downDirectionInteger(e);
                if (w == 0) {
                    leeway = "0";
                } else {
                    String sailStatus = trainingData.getSailStatus();
                    if ("U_P".equals(sailStatus)) {
                        leeway = w > 0 ? "-" + w : "+" + Math.abs(w);
                    } else if ("U_S".equals(sailStatus)) {
                        leeway = w > 0 ? "+" + w : "-" + Math.abs(w);
                    } else {
                        leeway = w > 0 ? "+" + w : "-" + Math.abs(w);
                    }
                }
            }
        }
        trainingData.setLeeway(leeway);
    }

    /**
     * 航行状态：
     * 当触发了风向和船首向计算时，刷新帆船图标；当船首向未获取到时，采用航向进行计算；
     * A= headingM - windDirection
     * ● 顺风左舷（D_P）：当 90° ＜ A ≤ 180°，或 -270° ＜ A ＜ -180°；
     * ● 顺风右舷（D_S）：当 -180° ＜ A＜ -90°，或 180° ＜ A ＜ 270°；
     * ● 迎风左舷（U_P）：当 0° ≤ A ≤ 90°，或 -360° ＜ A ≤ -270°；
     * ● 迎风右舷（U_S）：当 270° ≤ A ＜ 360°，或 -90° ≤ A ＜ 0°；
     * 姿态：
     * 滚转角x轴需要将所有原始值，取反:
     * ● 当帆船为迎风右舷（U_S）和顺风右舷（D_S）时，滚转角绝对值小于等于设定阈值时，则帆船平衡，当小于-设定阈值时帆船反扣，当大于设定阈值度时则帆船倾斜;
     * ● 当帆船为迎风左舷（U_P）和顺风左舷（D_P）时，滚转角绝对值小于等于设定阈值时，则帆船平衡，当小于-设定阈值时帆船倾斜，当大于设定阈值度时则帆船反扣;
     */
    public void sailStatusAndPosture(WindEnvWindowData windEnvWindowData,
                                     HeadingWindowData headingWindowData,
                                     RolWindowData rolWindowData,
                                     RolParam rolParam,
                                     TrainingData trainingData) {
        String sailStatus = "";

        if (Objects.nonNull(windEnvWindowData)) {
            Double windDirection = windEnvWindowData.getWindDirection();
            if (Objects.nonNull(windDirection)) {
                if (Objects.nonNull(headingWindowData)) {
                    Double headingM = headingWindowData.getHeadingM();
                    if (Objects.nonNull(headingM)) {
                        double a = headingM - windDirection;
                        sailStatus = sailStatusStr(a);
                    }
                }
            }
        }

        String posture = "";
        if (Objects.nonNull(rolWindowData)) {
            if (StrUtil.isNotBlank(sailStatus)) {
                Double rol = rolWindowData.getRol();
                if (Objects.nonNull(rol)) {
                    if (Math.abs(rol) <= rolParam.getRolDecisionThreshold()) {
                        posture = "平衡";
                    } else {
                        if ("U_S".equals(sailStatus) || "D_S".equals(sailStatus)) {
                            if (rol > 0) {
                                posture = "倾斜";
                            } else {
                                posture = "反扣";
                            }
                        } else {
                            if (rol > 0) {
                                posture = "反扣";
                            } else {
                                posture = "倾斜";
                            }
                        }
                    }
                }
            }
        }

        trainingData.setSailStatus(sailStatus);
        trainingData.setPosture(posture);
    }

    /**
     * 航行状态 判断
     */
    private static String sailStatusStr(double a) {
        // 顺风左舷
        if ((90 < a && a <= 180) || (-270 < a && a < -180)) {
            return "D_P";
        }
        // 顺风右舷
        if ((-180 < a && a < -90) || (180 < a && a < 270)) {
            return "D_S";
        }
        // 迎风左舷
        if ((0 <= a && a <= 90) || (-360 < a && a <= -270)) {
            return "U_P";
        }
        // 迎风右舷
        if ((270 <= a && a < 360) || (-90 <= a && a < 0)) {
            return "U_S";
        }
        return "-";
    }
}
